#include"ide.h"
#include"thread.h"
#include"interrupt.h"
#include"io.h"
#include"stdio.h"
#include"stdio_kernal.h"
#include"debug.h"
#include"timer.h"
#include"string.h"
#include"list.h"

#define DEV_MASTER 0
#define DEV_SLAVE 1
#define PRIMARY_CHANNEL_BASE 0x1F0
#define SECONDARY_CHANNEL_BASE 0x170
#define HD_CNT *((uint8_t*)0x475)

#define	BITS_MBS 0xa0 
#define BITS_LBA24_MOD 0x40
#define BITS_MASTER 0x0
#define BITS_SLAVE 0x10
#define CMD_IDENTIFY 0xEC
#define CMD_READ 0x20
#define CMD_WRITE 0x30
#define BSY_MASK 0x80
#define DRQ_MASK 0x08
#define ERR_MASK 0x01

#define reg_data(base) (base+0)
#define reg_sec_cnt(base) (base+2)
#define reg_lba_l(base) (base+3)
#define reg_lba_m(base) (base+4)
#define reg_lba_h(base) (base+5)
#define reg_dev(base) (base+6)
#define reg_com(base) (base+7)
#define reg_status(base) (base+7)

static void sector_read(struct disk*,char*,uint8_t);
static void sector_write(struct disk*,char*,uint8_t);
static bool busy_wait(struct disk*);
static void select_disk(struct disk*);
static void select_sector(struct disk*,uint32_t,uint8_t);
static inline void cmd_out(struct disk*,uint8_t);
static void identify_disk(struct disk*);
static void partition_scan(struct disk*,uint32_t);
static bool partition_info(list_elem*,int);

struct channel ata_channels[2];

static void intr_hd_handler(uint8_t vec_no){
	uint8_t ch_no=vec_no-0x2e;
	ASSERT(ata_channels[ch_no].irq==vec_no);
	if(ata_channels[ch_no].wait_intr){
		ata_channels[ch_no].wait_intr=false;
		sem_up(&ata_channels[ch_no].disk_done);
		inb(reg_status(ata_channels[ch_no].port_base));
	}
}

//wait until hard disk is not busy return true the request is done 
static bool busy_wait(struct disk* d){
	uint32_t timeout=30*1000; //time out 30 seconds
	uint8_t status;
	while(timeout){
		status=inb(reg_status(d->ch->port_base));
		if(!(status & BSY_MASK)){
			return status & DRQ_MASK;
		}
		msleep(10);
		timeout-=10;
	}
	return false;
}

struct disk* nm2disk(uint8_t ch_no,uint8_t dev_no){
	if(ch_no<2 && dev_no<2){
		return ata_channels[ch_no].devices+dev_no;
	}
	return NULL;
}

/*
 * as the hard disk sector count port is 8 bits so the hard disk 
 * can read write max 256 sector each command send
 * when set sector count with 0 it mean r/w 256 sector
 */
void ide_read(struct disk* d,char* buff,uint32_t lba,uint32_t s_cnt){
	uint32_t sec_op,sec_done;
	sec_op=sec_done=0;
	select_disk(d);
	while(sec_done<s_cnt){
		sec_op=sec_done+256<=s_cnt ? 256 : s_cnt-sec_done;
		select_sector(d,lba+sec_done,sec_op);
		cmd_out(d,CMD_READ);
		sem_down(&d->ch->disk_done);
		if(!busy_wait(d)){
			PANIC("hard disk read request timeout!");
		}
		sector_read(d,buff+sec_done*512,sec_op);
		sec_done+=sec_op;
	}
}

void ide_write(struct disk* d,char* buff,uint32_t lba,uint32_t s_cnt){
	uint32_t sec_op,sec_done;
	sec_op=sec_done=0;
	select_disk(d);
	while(sec_done<s_cnt){
		sec_op=sec_done+256<=s_cnt ? 256 : s_cnt-sec_done;
		select_sector(d,lba+sec_done,sec_op);
		cmd_out(d,CMD_WRITE);
		if(!busy_wait(d)){
			PANIC("hard disk write request timeout!");
		}
		sector_write(d,buff+sec_done*512,sec_op);
		sem_down(&d->ch->disk_done);
		sec_done+=sec_op;
	}
}

static void select_disk(struct disk* d){
	outb( (BITS_MBS) | (BITS_LBA24_MOD) | (d->dev_no==DEV_MASTER ? BITS_MASTER : BITS_SLAVE),reg_dev(d->ch->port_base));
}

static void select_sector(struct disk* d,uint32_t lba,uint8_t sec_cnt){
	uint8_t dev_data=0;
	outb(sec_cnt,reg_sec_cnt(d->ch->port_base));
	outb(lba&0xff,reg_lba_l(d->ch->port_base));
	outb(lba>>8&0xff,reg_lba_m(d->ch->port_base));
	outb(lba>>16&0xff,reg_lba_h(d->ch->port_base));
	dev_data=BITS_MBS|BITS_LBA24_MOD;
	dev_data|=d->dev_no==DEV_MASTER ? BITS_MASTER : BITS_SLAVE;
	dev_data|=lba>>24&0x0f;
	outb(dev_data,reg_dev(d->ch->port_base));
}

static inline void cmd_out(struct disk* d,uint8_t cmd){
	outb(cmd,reg_com(d->ch->port_base));	
	d->ch->wait_intr=true;
}


static void sector_read(struct disk* d,char* buff,uint8_t scnt){
	uint32_t s_bytes;
	//scnt 0 is 256 secotrs
	s_bytes= !scnt ? 256*512 : scnt*512;
	insw(buff,s_bytes/2,reg_data(d->ch->port_base));
}

static void sector_write(struct disk* d,char* buff,uint8_t scnt){
	uint32_t s_bytes;
	//scnt 0 is 256 secotrs
	s_bytes= !scnt ? 256*512 : scnt*512;
	outsw(buff,s_bytes/2,reg_data(d->ch->port_base));
}

#define HD_SN_OFFSET 10*2
#define HD_SN_LEN    10*2
#define HD_TYPE_OFFSET 27*2
#define HD_TYPE_LEN 40
#define HD_ALV_SECTORS_OFFSET 60*2

//send identify command get the hard disk info
static void identify_disk(struct disk* d){
	uint32_t alv_sector_cnt;
	char id_info[512];
	select_disk(d);
	cmd_out(d,CMD_IDENTIFY);
	sem_down(&d->ch->disk_done);
	if(!busy_wait(d)){
		PANIC("hard disk identify timeout!");
	}
	sector_read(d,id_info,1);
	char buffer[64];
	//the stack data is garbage so we should clear first
	memset(buffer,0,64);
	memcpy(buffer,id_info+HD_SN_OFFSET,HD_SN_LEN);
	printk("disk %s SN:%s\n",d->name,buffer);
	memset(buffer,0,64);
	memcpy(buffer,id_info+HD_TYPE_OFFSET,HD_TYPE_LEN);
	printk("disk %s type:%s\n",d->name,buffer);
	alv_sector_cnt=*(uint32_t*)(id_info+HD_ALV_SECTORS_OFFSET);
	printk("disk capacity %dM\n",alv_sector_cnt*512/1024/1024);
}

list partition_list;

static bool partition_info(list_elem* tag,int arg UNUSED){
	struct partition* part;
	part=elem2entry(struct partition,tag,tag);
	printk("partition name %s start_lba 0x%x sec_cnt 0x%x\n",part->name,part->start_lba,part->sec_cnt);
	return true;
}

//scan disk partition table
static void partition_scan(struct disk* d,uint32_t start_lba){
	static uint32_t p_no=0;
	static uint32_t l_no=0;
	static uint32_t ext_lba_base=0;
	static struct boot_sector boot_sec;
	struct partition_entry* pe=NULL;
	uint32_t partition_entry_index=0;
	
	ide_read(d,(char*)&boot_sec,start_lba,1);
	while(partition_entry_index<4){
		pe=boot_sec.dpt+partition_entry_index;
		if(pe->fs_type==0x5){
			if(start_lba==0){
				ext_lba_base=pe->start_lba;
				partition_scan(d,pe->start_lba);
			}else{
				partition_scan(d,ext_lba_base+pe->start_lba);
			}
		}else if(pe->fs_type!=0){
			if(start_lba==0){
				d->primary_part[p_no].start_lba=pe->start_lba;
				d->primary_part[p_no].sec_cnt=pe->sec_cnt;
				d->primary_part[p_no].disk=d;
				sprintf(d->primary_part[p_no].name,"%s%d",d->name,p_no+1);
				list_append(&partition_list,&d->primary_part[p_no].tag);
				p_no++;
			}else{
				//kernal now support at most 8 logic partitions
				ASSERT(l_no<8);
				d->logic_part[l_no].start_lba=start_lba+pe->start_lba;
				d->logic_part[l_no].sec_cnt=pe->sec_cnt;
				d->logic_part[l_no].disk=d;
				sprintf(d->logic_part[l_no].name,"%s%d",d->name,l_no+5);
				list_append(&partition_list,&d->logic_part[l_no].tag);
				l_no++;
			}
		}
		partition_entry_index++;
	}
}

void init_ide(void){
	uint32_t i,disk_index;
	uint32_t ch_cnt;
	disk_index=0;
	ch_cnt=div_round_up(HD_CNT,2);
	list_init(&partition_list);
	for(i=0;i<ch_cnt;i++){
		sprintf(ata_channels[i].name,"ide%d",i);
		ata_channels[i].port_base=(i==0 ? PRIMARY_CHANNEL_BASE : SECONDARY_CHANNEL_BASE);
		ata_channels[i].irq=(i==0 ? 0x20+14 : 0x20+15);
		init_semphore(&ata_channels[i].disk_done,0);
		register_intr_handler(ata_channels[i].irq,(intr_handler)intr_hd_handler);
		while(disk_index<HD_CNT){
			sprintf(ata_channels[i].devices[disk_index].name,"sd%c",'a'+disk_index);
			ata_channels[i].devices[disk_index].dev_no=disk_index==0 ? DEV_MASTER : DEV_SLAVE;
			ata_channels[i].devices[disk_index].ch=ata_channels+i;
			identify_disk(ata_channels[i].devices+disk_index);
			disk_index++;
		}
	}
	//scan slave disk partition table
	partition_scan(&ata_channels[0].devices[1],0);
	list_traversal(&partition_list,partition_info,0);
	printk("ide init done hard\n");
}
